package main

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	//"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"time"

	mqtt "github.com/eclipse/paho.mqtt.golang"
)

//define log interface
const (
	TIMEOUT = 60
)

var (
	logger  *log.Logger
	session = make(map[string]chan []byte)
	devices = make(map[string]map[string]interface{})
)

//define broker info
var broker mqtt.Client = mqtt.NewClient((&mqtt.ClientOptions{
	//ClientID:     "test",
	Username:     "server",
	Password:     "password",
	CleanSession: true,
	Order:        true,
	WillEnabled:  false,

	TLSConfig: tls.Config{
		InsecureSkipVerify: true,
		ClientAuth:         tls.NoClientCert,
	},
	AutoReconnect:         true,
	PingTimeout:           time.Second * 30,
	ConnectTimeout:        time.Second * 10,
	MaxReconnectInterval:  time.Second * 60,
	DefaultPublishHandler: handle_message,
	OnConnect: func(cli mqtt.Client) {
		logger.Println("Broker connect success")
	},
	OnConnectionLost: func(cli mqtt.Client, err error) {
		logger.Println("Broker connection lost :" + err.Error())
	},
}).AddBroker("ssl://127.0.0.1:8883"))

//}).AddBroker("ssl://yun.wisiyilink.com:8883"))

func handle_message(cli mqtt.Client, msg mqtt.Message) {
	//match regexp to choose router
	s := regexp.MustCompile("\\Av1/w/([[:alnum:]]+)/([[:alnum:]]+)(/.+)?\\z").FindStringSubmatch(string(msg.Topic()))
	if s == nil {
		logger.Println("Error topic: " + msg.Topic())
		return
	}

	id := s[1]
	secret := s[2]
	method := s[3]

	switch method {
	case "":
		//process alive
		data := make(map[string]interface{})
		if json.Unmarshal(msg.Payload(), &data) != nil {
			logger.Println("Error process json struct")
			return
		}
		//check secret
		if data["secret"] == secret {
			devices[id] = data
		}
	default:
		//send msg to channel(none block)
		select {
		case session[msg.Topic()] <- msg.Payload():
		default:
		}
	}
	return
}

func run_broker() {
	logger.Println("Start Broker...")
	for {
		token := broker.Connect()
		if token.Wait() && token.Error() != nil {
			fmt.Println(token.Error())
			//sleep 1 sec if connect fail
			time.Sleep(time.Second)
		} else {
			break
		}
	}

	logger.Println("Subscribe broker topic")
	if token := broker.SubscribeMultiple(map[string]byte{
		"v1/w/#": 2,
	}, nil); token.Wait() && token.Error() != nil {
		logger.Fatal(token.Error())
	}

	logger.Println("Broker stared...")

	return
	//broker.Disconnect(250)
	//os.Exit(0)
}

func build_res(ret int, msg string) []byte {
	data := make(map[string]interface{})
	data["ret"] = ret
	data["msg"] = msg
	res, err := json.Marshal(data)
	if err != nil {
		logger.Println("Error make json : " + err.Error())
		return []byte("")
	}
	return res
}

func run_http() {
	logger.Println("Start Http...")

	//define /v1/router
	http.HandleFunc("/api/v1/", func(w http.ResponseWriter, r *http.Request) {
		s := regexp.MustCompile("\\A/api/v1/([[:alnum:]]+)/([[:alnum:]]+)/(.+)\\z").FindStringSubmatch(r.RequestURI)
		if s == nil {
			w.Write(build_res(-1, "Error router"))
			return
		}

		//check token alive
		id := s[1]
		secret := s[2]
		method := s[3]
		switch {
		case devices[id] == nil:
			w.Write(build_res(-1, "Device not exist"))
			return
		case devices[id]["secret"] != secret:
			w.Write(build_res(-1, "Secret error"))
			return
		case devices[id]["status"] == "offline":
			w.Write(build_res(-1, "Device offline"))
			return
		}

		//init msg
		msg := []byte("")
		switch r.Method {
		case "GET":
		case "POST":
			msg, _ = ioutil.ReadAll(r.Body)
		default:
			fmt.Fprint(w, build_res(-1, "Error method"))
			return
		}

		//Create session
		random := strconv.FormatInt(time.Now().UnixNano(), 10)
		rTopic := "v1/r/" + id + "/" + secret + "/" + method + "/" + random
		wTopic := "v1/w/" + id + "/" + secret + "/" + method + "/" + random

		//make channel
		channel := make(chan []byte)
		defer close(channel)
		session[wTopic] = channel
		defer delete(session, wTopic)

		//Publish data
		broker.Publish(rTopic, 2, false, msg)

		//catch msg
		select {
		case msg = <-channel:
			w.Write(msg)
		case <-time.After(time.Second * TIMEOUT):
			w.Write(build_res(-1, "Timeout"))
		}
	})

	//define /v1
	http.HandleFunc("/api/v1", func(w http.ResponseWriter, r *http.Request) {
		//fmt.Fprintln(w, devices)
		res, err := json.MarshalIndent(devices, "", "\t")
		if err != nil {
			logger.Println("Error make json : " + err.Error())
		}
		w.Write(res)
	})
	//Start listing...
	err := http.ListenAndServe("127.0.0.1:81", nil)
	if err != nil {
		log.Fatal("ListenAndServe : ", err)
	}
}

func main() {
	//Create log file
	logFile, err := os.Create("/dev/stdout") //stdout for test
	if err != nil {
		fmt.Println(err)
	}

	//set flags to logger
	logger = log.New(logFile, "", log.LstdFlags|log.Lshortfile)

	//switch mqtt debug option
	if false {
		mqtt.DEBUG = log.New(os.Stdout, "", 0)
		mqtt.DEBUG.SetFlags(log.LstdFlags)
		mqtt.ERROR = log.New(os.Stdout, "", 0)
		mqtt.ERROR.SetFlags(log.LstdFlags)
	}

	//enter broker main function
	run_broker()
	run_http()
}
